从2167到6079TPS的一次vLLM 950单卡压测
前言
最近在 Ascend 950PR 上部署 Qwen3.5-4B,最开始直连 vLLM 压测只有:
1 | Total TPS: 2167.47 tok/s |
设备利用率偶尔能到 100%,但全程平均只有 26.2%,HBM 也只用了约 40%。显然当前配置没有把服务喂满。
后来在 --enforce-eager 下,最高稳定结果做到:
1 | Total TPS: 6078.81 tok/s |
大约是最初的 2.8 倍
TPS
vllm bench serve 同时给出 Total token throughput 和 Output token throughput。
Total TPS = (input token + output token) / time
Output TPS = output token / time
调大batch时撞上了72GiB OOM
把 max-num-batched-tokens 从 8192 提到 16384 后,vLLM 在启动 profile 阶段直接报:
1 | NPU out of memory. Tried to allocate 72.00 GiB |
16k 上下文显然不应该平白消耗 72 GiB,这个数字明显是有问题的
沿堆栈追到 LoRA 的 PyTorch fallback 后发现,问题出在高级索引:
1 | selected_loras = lora_b_weights[lora_indices_tensor].to( |
当时的形状为:
1 | max_num_batched_tokens = 16384 |
代码把 LoRA-A 权重按每个 token 展开成:
1 | [16384, 1, 128, 9216] |
元素总数:
1 | 16384 × 1 × 128 × 9216 |
高级索引先产生一份 BF16 临时张量:
1 | 19,327,352,832 × 2 bytes = 36 GiB |
随后 .to(float32) 又要申请:
1 | 19,327,352,832 × 4 bytes = 72 GiB |
日志里的 72 GiB 正好对上。
正常 grouped SGMV 不应该给每个 token 复制整份 [128,9216] 权重。它真正需要的 LoRA-A 只有约 2.25 MiB/slot,输出 buffer 也只有约 8 MiB。
所以这不是 16k 上下文的正常显存需求,也不是 KV Cache 太大,而是 rank-128 触发的 fallback 实现把权重按 token 展开了。
他给每个位置的token都复制了一份lora weight, 真是垃圾的实现
暂时关闭LoRA,单独测base model上限
这轮目标是测 base model 吞吐,不需要让 LoRA profile 干扰结果,因此先临时关闭 LoRA,把 batch token 提到能覆盖输入的范围,再扫描客户端并发。
服务端仍限制 32 active seq 时:
| 客户端并发 | Total TPS | Output TPS | 现象 |
|---|---|---|---|
| 16 | 2439.85 | 480.17 | 尚未饱和 |
| 32 | 2915.96 | 573.88 | 明显提升 |
| 64 | 4092.61 | 805.44 | 排队请求减少调度空洞 |
| 128 | 4218.27 | 830.10 | 接近平台 |
| 256 | 4265.51 | 839.39 | 只再提升约1.1% |
这里有个很实用的现象:客户端并发高于服务端 active seq 并不一定没用。多一些排队请求可以让完成的 sequence 立刻被新请求补上,提高持续利用率,代价是 TTFT 变大。
继续提高服务端active seq
关闭 LoRA 后 KV Cache 有足够余量,于是继续按比例增加:
| active seq | max batched tokens | 客户端并发 | Total TPS | Output TPS |
|---|---|---|---|---|
| 32 | 16384 | 256 | 4265.51 | 839.39 |
| 64 | 32768 | 256 | 5617.57 | 1105.46 |
| 128 | 65536 | 256 | 6078.81 | 1196.22 |
最高稳定档的完整数据:
1 | Successful requests: 256 |
对应设备状态:
1 | 平均利用率:45.6% |
吞吐提高了,但 TTFT 也到了约 16 秒。批处理越大,硬件效率通常越高,请求排队也越久。
为什么没有继续堆到256
我也试了更大的服务端 batch:
1 | 256 active seq / 131072 tokens |
启动 profile 触发 Ascend 算子限制:
1 | coreDim=65536,要求 <=65535 |
只能说 Ascend 和 vllm 之间的适配还需要时间, 测试过程问题很多
从2167到6079TPS的一次vLLM 950单卡压测
https://blog.novashen.top/2026/07/21/tech/AI Infra/从2167到6079TPS的一次vLLM单卡压测/